package assignment.ton.def;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.RoundRectangle2D;
import java.io.Serializable;
import java.util.ArrayList;

import javax.swing.JComponent;

import assignment.ton.gui.MainFrame;
import assignment.ton.gui.interfaces.EditingInterface;
/**
 * This is the must important class it contains all of the data for each of the classes
 * It contains arraylists containing links, parameters and methods.
 */
public class Class extends JComponent implements Serializable,EditingInterface{
	private static final long serialVersionUID = 8504444864776767351L;
	public static Class saveItem;
	private int x,y, textWidth;
	private int sX = 100; //please be mult of 2
	private int sY = 50; //pleas be mult of 2
	private ArrayList<Parameter> constructorParameters;
	private ArrayList<ClassLink> linksList;
	private ArrayList<Variable> varList;
	private ArrayList<Method> methodList;
	private ArrayList<String> extraCode = new ArrayList<String>();
	private final Font textFont = new Font("Arial", Font.BOLD, 14);
	private Color theColor;
	private final String threeDots = "...";
	private boolean oldG = false;
	private String sensibleName;
	private String nameOut;
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
		oldG=false;
	}
	public Class(int getX, int getY, Color c, MainFrame mF){
		x=getX; /** The x position o the center of the box */
		y=getY; /** The y position of the centr of the box */
		theColor = c;
		constructorParameters = new ArrayList<Parameter>(); /** The list of constructor parameters */
		linksList = new ArrayList<ClassLink>(); /** The list of links to other classes */
		varList = new ArrayList<Variable>();
		methodList = new ArrayList<Method>();
	}
	/**
	 * @return the x position of the center
	 */
	public int getX() {
		return x;
	}
	/**
	 * @param x sets the x position of the center
	 */
	public void setX(int x) {
		this.x = x;
	}
	/**
	 * @return the y position of the center
	 */
	public int getY() {
		return y;
	}
	/**
	 * @param y sets the position to y
	 */
	public void setY(int y) {
		this.y = y;
	}
	/**
	 * @param c the color for it and links to be set to
	 */
	public void setColor(Color c){
		theColor = c;
	}
	/**
	 * This method is responsible for creating the text in the boxes.
	 * @param g graphics 2d object
	 */
	public void updateDisplayText(Graphics g){
		nameOut = this.getName();
		FontMetrics metrics = g.getFontMetrics(textFont);
		textWidth = metrics.stringWidth(nameOut);
		int dotWidth = metrics.stringWidth(threeDots);
		if(textWidth>sX){
			while(textWidth>(sX-dotWidth)){
				nameOut = nameOut.substring(0,nameOut.length()-1);
				textWidth = metrics.stringWidth(nameOut);
			}
			textWidth = textWidth+dotWidth;
			nameOut = nameOut + threeDots;
		}
		sensibleName = nameOut;
	}
	public void setExtraCode(ArrayList<String> ar){
		extraCode = ar;
	}
	public ArrayList<String> getExtraCode(){
		return extraCode;
	}
	/**
	 * This is the main paint method creating the rectangles and calling the methods to draw links and text
	 */
	public void paint(Graphics2D g){
		paint((Graphics)g);
	}
	public void paint(Graphics g){
		if(oldG==false){
			updateDisplayText(g);
			oldG = true;
		}
		g.setColor(theColor);
		Graphics2D graphics2 = (Graphics2D) g;
		this.paintLink(graphics2);
		RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(x-sX/2,y-sY/2,sX,sY, 5, 5);
		graphics2.fill(roundedRectangle);
        graphics2.draw(roundedRectangle); 
		g.setColor(Color.BLACK);
		g.setFont(textFont);
		g.drawString(nameOut, x-textWidth/2, y);
		super.paint(g);
	}
	/**
	 * Checks to see if a point is inside the rectangle of the class
	 * @param mousePoint the current position of the mouse
	 * @return true if it is in the rectangle
	 */
	public boolean cursorInBox(Point2D mousePoint){
		Point2D topLeft = new Point2D.Double(x-sX/2,y-sY/2);
		Point2D bottomRight = new Point2D.Double(x+sX/2,y+sY/2);
		if(Util.isInRectnalge(topLeft, bottomRight, mousePoint)){;
			return true;
		}else{
			return false;
		}
	}
	/**
	 * Gets a parameter object from it's name
	 * @param n the name fo the parameter to find
	 * @return returns the parameter object
	 */
	public Parameter findConstructorParameter(String n){
		Parameter returnParameter = null;
		for(Parameter p: constructorParameters){
			if(p.getName().equals(n)){
				returnParameter = p;
			}
		}
		return returnParameter;
	}
	/**
	 * adds a constructor parameter to a list if its not there
	 * @param p the parameter to add
	 */
	public void addConstructorParameter(Parameter p){
		if(findConstructorParameter(p.getName())==null){
			constructorParameters.add(p);
		}
	}
	/**
	 * @return the arraylist of constructor parameters
	 */
	public ArrayList<Parameter> getConstructorParameters(){
		return constructorParameters;
	}
	/**
	 * @param n the parameter with name to be removed
	 */
	public void removeConstructorParameter(String n){
		Parameter p = findConstructorParameter(n);
		if(p!=null){
			constructorParameters.remove(p);
		}
	}
	/**
	 * This is for the dropdown menu of constructor parameters
	 * @return the list of parameters in string form
	 */
	public String[] getConstructorList(){
		int i = 0;
		String[] listData;
		if(constructorParameters.size()>0){
			listData = new String[constructorParameters.size()];
			for(Parameter p: constructorParameters){
				listData[i] = p.toString();
				i++;
			}
		}else{
			listData = new String[1];
			listData[0]="";
		}
		return listData;
	}
	/**
	 * @return the sensible name, ie the one tha fits in the box
	 */
	
	public String getSensibleName(){
		return sensibleName;
	}
	//this section is for the linking code
	/**
	 * Checks to see if this class has a link with the other one
	 * @param c the class to check against
	 * @return true if this has the other class
	 */
	public boolean hasClass(Class c){
		if(linksList.size()<1){ return false; }
		for(ClassLink d: linksList){
			if(d.getLink()==c){
				return true;
			}
		}
		return false;
	}
	/**
	 * @param c the class to be looked up
	 * @return the class link object from the name
	 */
	public ClassLink getLink(Class c){
		ClassLink returnVal = null;
		if(linksList.size()<1){ return null; }
		for(ClassLink d: linksList){
			if(d.getLink()==c){
				returnVal = d;
			}
		}
		return returnVal;
	}
	public ArrayList<ClassLink> getLinkList(){
		return linksList;
	}
	/**
	 * Adds a link if it doesnt exist
	 * @param cL the class link object to add
	 * @return true if the link was added successfully
	 */
	public boolean addLink(ClassLink cL){
		if(!hasClass(cL.getLink())){
			linksList.add(cL);
			return true;
		}
		return false;
	}
	/**
	 * Deletes a link
	 * @param c the class object to have its link deleted
	 * @return tru if successfully removed
	 */
	public boolean deleteLink(Class c){
		if(!hasClass(c)){ return false; }
		if(linksList.size()<1){ return false; }
		ClassLink toDelete = null;
		for(ClassLink d: linksList){
			if(d.getLink()==c){
				toDelete = d;
			}
		}
		if(toDelete!=null){
			linksList.remove(toDelete);
			return true;
		}
		return false;
	}
	/**
	 * This class contains the point on a side and what side it actually is
	 * @author Tim
	 *
	 */
	class SideClass{
		private SideEnum num;
		private Point2D point;
		public SideClass(SideEnum thenum,Point2D thepoint){
			num = thenum;
			point = thepoint;
		}
		public SideEnum getSide(){
			return num;
		}
		public Point2D getPoint(){
			return point;
		}
	}
	/**
	 * This enum is just to define sides
	 * @author Tim
	 *
	 */
	enum SideEnum{
		UP,
		DOWN,
		LEFT,
		RIGHT
	}
	/**
	 * of the 16 possible cambinations gets the cloest side to the other class
	 * @param theClass
	 * @return it then returns that side object
	 */
	public SideClass getClosestSide(Class theClass){
		ArrayList<SideClass> thisClass = new ArrayList<SideClass>();
		ArrayList<SideClass> otherClass = new ArrayList<SideClass>();
		thisClass.add(new SideClass(SideEnum.LEFT,new Point2D.Float(this.getX()-sX/2,this.getY())));
		thisClass.add(new SideClass(SideEnum.RIGHT,new Point2D.Float(this.getX()+sX/2,this.getY())));
		thisClass.add(new SideClass(SideEnum.UP,new Point2D.Float(this.getX(),this.getY()-sY/2)));
		thisClass.add(new SideClass(SideEnum.DOWN,new Point2D.Float(this.getX(),this.getY()+sY/2)));
		otherClass.add(new SideClass(SideEnum.LEFT,new Point2D.Float(theClass.getX()-sX/2,theClass.getY())));
		otherClass.add(new SideClass(SideEnum.RIGHT,new Point2D.Float(theClass.getX()+sX/2,theClass.getY())));
		otherClass.add(new SideClass(SideEnum.UP,new Point2D.Float(theClass.getX(),theClass.getY()-sY/2)));
		otherClass.add(new SideClass(SideEnum.DOWN,new Point2D.Float(theClass.getX(),theClass.getY()+sY/2)));
		SideClass returnSide = thisClass.get(0);
		for(SideClass op: otherClass){
			for(SideClass p: thisClass){
				if(p.getPoint().distance(op.getPoint())<returnSide.getPoint().distance(op.getPoint())){
					returnSide = p;
				}
			}
		}
		return returnSide;
	}
	/**
	 * This class calculates the best route to take between the 2 closest sides choosing either 2 or 3 lines
	 * @param graphics2 just pass graphics 2d object to it
	 */
	public void paintLink(Graphics2D graphics2){
		if(linksList.size()>0){
			for(ClassLink cL: linksList){
				double a,b,c,d;
				SideClass classA, classB;
				classA = this.getClosestSide(cL.getLink());
				classB = cL.getLink().getClosestSide(this);
				a = classA.getPoint().getX(); //this x
				b = classA.getPoint().getY(); //this y
				c = classB.getPoint().getX(); //other class x
				d = classB.getPoint().getY(); //other class y
				if((classA.getSide()==SideEnum.LEFT&&classB.getSide()==SideEnum.RIGHT)||(classA.getSide()==SideEnum.RIGHT&&classB.getSide()==SideEnum.LEFT)){
					//line across 1 y1/y2 = b x1 = a x2 = (a+b)/2
					//line down x1/x2 = (a+b)/2 y1 = b y2= d
					//line across 2 y1/y2 = d x1= (a+b)/2 x2 = c
					graphics2.draw(new Line2D.Double(a,b,(a+c)/2,b));
					graphics2.draw(new Line2D.Double((a+c)/2,b,(a+c)/2,d));
					graphics2.draw(new Line2D.Double((a+c)/2,d,c,d));
				}
				
				if((classA.getSide()==SideEnum.LEFT||classA.getSide()==SideEnum.RIGHT)
						&&(classB.getSide()==SideEnum.UP||classB.getSide()==SideEnum.DOWN)){
					graphics2.draw(new Line2D.Double(a,b,c,b));
					graphics2.draw(new Line2D.Double(c,b,c,d));
				}
				if((classA.getSide()==SideEnum.UP||classA.getSide()==SideEnum.DOWN)
						&&(classB.getSide()==SideEnum.LEFT||classB.getSide()==SideEnum.RIGHT)){
					graphics2.draw(new Line2D.Double(a,b,a,d));
					graphics2.draw(new Line2D.Double(a,d,c,d));
				}
				if((classA.getSide()==SideEnum.UP&&classB.getSide()==SideEnum.DOWN)
						||(classA.getSide()==SideEnum.DOWN&&classB.getSide()==SideEnum.UP)){
					//line down 1 x1/x2 = a y1 = b y2 = (b+d)/2
					//line accross y1/y2 = (b+d)/2 x1 = a x2 = c
					//line down 2 x1/x2 = c y1 = (b+d)/2 y2 = d
					graphics2.draw(new Line2D.Double(a,b,a,(b+d)/2));
					graphics2.draw(new Line2D.Double(a,(b+d)/2,c,(b+d)/2));
					graphics2.draw(new Line2D.Double(c,(b+d)/2,c,d));
				}
				//and here we add the cardinalities
				String isMany = "0..1";
				if(cL.getMany()){ isMany = "0..*"; }
				int mX = 0, mY = 0;
				if(classB.getSide()==SideEnum.LEFT){ mX = (int)classB.getPoint().getX()-30; mY = (int)classB.getPoint().getY()-2; }
				if(classB.getSide()==SideEnum.RIGHT){ mX = (int)classB.getPoint().getX()+2; mY = (int)classB.getPoint().getY()-2; }
				if(classB.getSide()==SideEnum.UP){ mX = (int)classB.getPoint().getX()+7; mY = (int)classB.getPoint().getY()-2; }
				if(classB.getSide()==SideEnum.DOWN){ mX = (int)classB.getPoint().getX()-30; mY = (int)classB.getPoint().getY()+12; }
				graphics2.setFont(textFont);
				graphics2.drawString(isMany, mX, mY);
			}
		}
	}
	/////////////////////////////////////
	//This section is for variable code
	/**
	 * @return an array of strings describing the variables
	 */
	public String[] getVariableList(){
		int i = 0;
		String[] listData;
		if(varList.size()>0){
			listData = new String[varList.size()];
			for(Variable v: varList){
				listData[i] = v.toString();
				i++;
			}
		}else{
			listData = new String[1];
			listData[0]=null;
		}
		return listData;
	}
	/**
	 * has this variable
	 * @param varName the variable name
	 * @return true if it has the variable
	 */
	public boolean hasVariable(String varName){
		for(Variable v: varList){
			if(v.getName().equals(varName)){ return true; }
		}
		return false;
	}
	/**
	 * Add variable to list
	 * @param v the variable to add
	 * @return true if successful, fail if already in
	 */
	public boolean addVariable(Variable v){
		if(hasVariable(v.getName())==false){
			varList.add(v);
			return true;
		}
		return false;
	}
	/**
	 * @param n variable with name to find
	 * @return the variable with name passed in
	 */
	public Variable findVariableByName(String n){
		for(Variable v: varList){
			if(v.getName().equals(n)){ return v; }
		}
		return null;
	}
	/**
	 * Remove variable by name
	 * @param n the name of the variable to remove
	 * @return true if one has been removed
	 */
	public boolean removeVariable(String n){
		if(hasVariable(n)==false){ return false; }
		varList.remove(findVariableByName(n));
		return true;
	}
	public ArrayList<Variable> getVariableArrayList(){
		return varList;
	}
	/////////////////////////////////////
	//this section is for the methods
	public String[] getMethodList(){
		int i = 0;
		String[] listData;
		if(methodList.size()>0){
			listData = new String[methodList.size()];
			for(Method m: methodList){
				listData[i] = m.toString();
				i++;
			}
		}else{
			listData = new String[1];
			listData[0]=null;
		}
		return listData;
	}
	public boolean hasMethod(String m){
		if(methodList.size()<1){ return false; }
		for(Method me: methodList){
			if(me.getName().equals(m)){
				return true;
			}
		}
		return false;
	}
	public boolean addMethod(Method m){
		if(hasMethod(m.getName())){ return false; }
		methodList.add(m);
		return true;
	}
	public Method getMethodByName(String n){
		for(Method me: methodList){
			if(me.getName().equals(n)){
				return me;
			}
		}
		return null;
	}
	public void removeMethod(Method m){
		methodList.remove(m);
	}
	public ArrayList<Method> getMethodArrayList(){
		return methodList;
	}
	///////////////////////////////////
}
